﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace Wireframe
{
    /// <summary>
    ///     Represents an entry point to register event handlers.
    /// </summary>
    public sealed class WireHub
    {
        /// <summary>
        ///     Collection of event handlers.
        /// </summary>
        private readonly EventHandlers _eventHandlers;

        /// <summary>
        ///     Collection of object participating in event messaging.
        /// </summary>
        private readonly ConcurrentDictionary<Type, object> _registrations = new ConcurrentDictionary<Type, object>();

        /// <summary>
        ///     Initializes new event passing entry point.
        /// </summary>
        /// <param name="eventHandlers">Event handlers collection.</param>
        public WireHub(EventHandlers eventHandlers)
        {
            _eventHandlers = eventHandlers;
        }

        /// <summary>
        ///     Registers an instance of an event handler allowing it to respond to events.
        /// </summary>
        /// <typeparam name="T">Type of event handler containing callbacks.</typeparam>
        /// <param name="instance">Instance of event handler.</param>
        /// <returns>Registered instance of an event handler.</returns>
        public T Register<T>(T instance)
        {
            _registrations.TryAdd(typeof (T), instance);
            return instance;
        }

        /// <summary>
        ///     Unregisters an instance of an event handler excluding it from event receivers.
        /// </summary>
        /// <typeparam name="T">Type of event handler containing callbacks.</typeparam>
        /// <param name="instance">Instance of event handler.</param>
        /// <returns>Unregistered instance of an event handler.</returns>
        public T Unregister<T>(T instance)
        {
            object inst;
            _registrations.TryRemove(typeof (T), out inst);
            return instance;
        }

        /// <summary>
        ///     Sends an event.
        /// </summary>
        /// <typeparam name="T">Type of message being sent.</typeparam>
        /// <param name="message">Instance of event message.</param>
        /// <returns>Task for completion.</returns>
        public async Task Send<T>(T message)
        {
            await Task.WhenAll(await Task.Run(() => Broadcast(message)));
        }

        /// <summary>
        ///     Broadcasts the event message.
        /// </summary>
        /// <typeparam name="T">Type of event.</typeparam>
        /// <param name="message">Instance of event message data.</param>
        /// <returns>Enumeration of tasks with result.</returns>
        private IEnumerable<Task<object>> Broadcast<T>(T message)
        {
            ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>> handlers;
            if (_eventHandlers.Handlers.TryGetValue(typeof (T), out handlers))
            {
                foreach (var registration in _registrations)
                {
                    ConcurrentBag<MethodInfo> callbacks;
                    if (handlers.TryGetValue(registration.Key, out callbacks))
                    {
                        foreach (MethodInfo callback in callbacks)
                        {
                            KeyValuePair<Type, object> reg = registration;
                            MethodInfo call = callback;
                            yield return Task.Run(() => call.Invoke(reg.Value, new object[] {message}));
                        }
                    }
                }
            }
        }
    }
}